<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
	<meta http-equiv="pragma" content="no-cache" />
    <meta name="author" content="Jayli" />
	<meta name="keywords" content="淘宝UED 前端工程师 拔赤" />	
	<meta name="description" content="淘宝前端工程师拔赤，关注尖端前端技术，关注yui" />
	<meta http-equiv="imagetoolbar" content="no" />
	<meta name="title" content="拔赤的文章列表" />
    <title>YUI3设计中的激进和妥协</title>
    <link href="http://jayli.github.com/blog/atom.xml" rel="alternate" title="setImpl" type="application/atom+xml" />
    <link rel="stylesheet" href="http://jayli.github.com/blog/media/css/style.css">
    <link rel="stylesheet" href="http://jayli.github.com/blog/media/css/github.css">
	<style>
	</style>
    <script type="text/javascript" src="http://jayli.github.com/blog/media/js/highlight.pack.js"></script>
    <script type="text/javascript">
      hljs.initHighlightingOnLoad();
    </script>
	<!--[if lte IE 8]>
	<script src="http://a.tbcdn.cn/apps/lottery/00023/index-v3/js/html5.js"></script>
	<![endif]-->
	<meta name="baidu-tc-verification" content="afb6c9df553ef3493d9ee65263df0d55" />
	<script src="http://siteapp.baidu.com/static/webappservice/uaredirect.js" type="text/javascript"></script><script type="text/javascript">uaredirect("http://m.zoojs.org", "http://jayli.github.com/blog");</script>
  </head>
  <body>
    <div id="container">
      <div id="main" role="main">
        <header>
        <h1>YUI3设计中的激进和妥协</h1>
        </header>
        <nav>
        <span><a title="home page" class="" href="/blog/index.html">home</a></span>
        <span><a title="tags" class="" href="/blog/tags.html">tags</a></span>
        <span><a title="about" class="" href="/blog/about.html">about me</a></span>
        <span><a title="flickr" class="" href="http://www.flickr.com/photos/lijing00333">flickr</a></span>
        <span><a title="blogroll" class="" href="/blog/links.html">links</a></span>
        <span><a title="projects" class="" href="/blog/projects.html">projects</a></span>
        <span><a title="subscribe me" class="" href="/blog/atom.xml">feed</a></span>
        </nav>
        <article class="content">
        <section class="post">
<p>相信每个前端工程师都有自己喜爱的 javascript框架，说情感也好，道信仰也罢，javascript框架带给人的不仅仅是便捷的开发，更有一种纯粹的逻辑美感，不管是jquery 曼妙的简洁，还是yui魔术般的沙箱，都使我们产生无穷的想象。然而，js框架却又必然无法做到面面俱到的完美无瑕，比如jquery在OO方面做出的让步，以及yui在性能上做的牺牲，无不给人传达一种缺憾美、一种理想的现实主义。今天，我们来看看yui3在框架设计中的这些牺牲和让步，以便让我们更加深刻的理解yui3的全貌，并将其优势发挥至最佳。</p>

<h3>种子的一步到位 or 颗粒化</h3>

<p>所谓种子一步到位是指只要在页面引入一个种子文件的script标签，比如prototype和jquery，只要引入一个prototype.js或 jquery.js就可以了，他们将各自对dom操作和event的封装等等都囊括进一个文件中，并尽力将其做到最小，这样做的好处是显而易见的，使用框架非常简单。然而yui将这些功能做了级别划分和颗粒化设计，从概念上抽象出来“核心”、“工具”和“组件”，每个小功能放在一个文件当中，需要的时候则要自行去引用，yui文档中给出的大量demo都采用这种方法，这种设计显然不像jquery那样对初学者友好，而且使用起来不够傻瓜，为了实现一个小功能，甚至要引入三四个js文件。yui这样做的原因有两个，一是yui实在太大，把所有功能都搞进一个文件中确实有点不靠谱，二是为其动态加载的框架设计做铺垫。</p>

<h3>手动引入 or 动态加载</h3>

<p>往页面中写js的传统方法是，直接将js文件作为script标签路径写在页面中，使用yui也可以这样引入页面，但yui更推荐使用loader进行动态加载。动态加载脚本的渊源很复杂，目前来看主要原因有三，其一，页面中手写js标签无论如何都会占用一个http请求，即使这个请求是一个304，动态加载的文件缓存后则不必发起真实的http请求，其二，动态加载可以实现按需加载，而且可以根据js文件之间的依赖进行去重和排序，手写标签加载js文件则必须让开发者去额外关注一下文件的排序、重复等等，其三，动态加载有利于页面代码的语义化，这使得开发者只关心“需要什么”，而不用去在意“如何得到”。当项目变得越发臃肿，维护成本越来越高的时候，这中小技巧会有不小的好处的。</p>

<h3>逻辑启动的单一入口 or 沙箱</h3>

<p>我们在页面中启动一个js逻辑通常是放在一个类似onDomReady的方法中，如果页面中存在多个逻辑的时候怎么办呢？比如，a实现了逻辑A，页面代码是这样的</p>

<pre><code>$.onDomReady(function(){
    LogicA.start();
});
</code></pre>

<p>这段代码通常写在页面的尾部，这段逻辑所伴随的html代码是埋藏在页面的某处，这时b要在页面中增加逻辑B，b的做法是首先找到尾部的这段代码，然后更改成如下模样：</p>

<pre><code>$.onDomReady(function(){
    LogicA.start();
    LogicB.start();
});
</code></pre>

<p>同样，B逻辑所伴随的html代码也是埋藏在页面的某处，这样看来，js逻辑和其伴随的html代码如此分离，以至于到了删减功能的时候，往往删掉 html却忘了删js，或者删掉js忘了删除html，在重用代码的时候也会是个麻烦。同样，在调试的时候，工程师也要打开两个窗口分别关注js和 html，即使这两段代码在同一个文件当中。如此则不如把代码写成这样：</p>

<pre><code>$.onDomReady(function(){
    LogicA.start();
});

…

$.onDomReady(function(){
    LogicB.start();
});
</code></pre>

<p>这种coding写法正是yui所提倡的，也就是所谓的沙箱，每个逻辑包含在一个沙箱中，各司其则互不干扰。当第三者浏览代码的时候也立即明白这就是一个独立的功能块，包含典型的html结构和启动逻辑的js，要重用，整块拷走即可。</p>

<h3>代码污染 or 沙箱</h3>

<p>刚才提到的沙箱可以解决一部分代码污染的问题，新人阅读代码不用再看着乱码般的源码，“瞻前顾后”小心翼翼的寻找html和js的对应关系。但这种写法也存在隐患，现在的前端开发越来越复杂也更多的使用分层，比如底层使用yui，中间层是自定义的工具库，或者再加一个项目的widget组件库，写页面逻辑则是基于这些库进行开发。这样的话，每段逻辑可能写成这个样子：</p>

<pre><code>$.onDomReady(function(){
    LogicA.start();
});
</code></pre>

<p>尽管我们可以约定，将项目用到的所有的组件都统一加载进页面中，但当组件越来越多的时候，就出现了上文所说的一步到位和颗粒化之间的矛盾，当每个控件单独占用一个js文件和与之相伴随的css皮肤，一个相对复杂的逻辑就变成了上文所说的手动引入js文件，并随之引入一些显而易见的弊端：</p>

<pre><code>$.onDomReady(function(){
    LogicA.start();
});
</code></pre>

<p>首先，手写大量的js文件会各自占用单独的http请求，在者，这个场景中，slider.js继承自tabview.js，因此要着重关注他们的顺序，第三，如果别人在页面的其他地方也使用了日历或者幻灯，还要再加两个相同的js标签？其实，说到这里，我们已经可以隐约看到大项目团队开发的影子了，在一个迭代及其频繁的项目中，开发者需要在短的时间内完成一个复杂页面的某个功能的开发，而且不能影响到页面的其他功能，毕竟，每添加一个功能，QA mm们都要将与之牵连的所有功能都要回归，可辛苦了我们的QA mm们。在这种情况下，一个功能的开发可能和同一个页面其他功能的开发相互并行。互相不属于同一个项目组，也不知晓对方的实现。这种模式则是我们经常遇到的多人开发，冲突也大都由此产生，每个功能单独看来是正确的，合并到一起会产生冲突和bug，这时调试bug则需要两个工程师同时进行，很麻烦。甚者，当组件升级了，比如，tabview.js再继承自tab.js，则又要去联系各个工程师，将每个引用tabview.js的地方之前再加上一个 tab.js，很麻烦。我们说，这种多人协作模式所带来的冲突也是代码污染的一种，因为每个人只能掌控类似tms区块那么巴掌大的地方，所组成的最终页面是什么样，都不知道。更何况，这种生猛的引用js，也会阻塞页面加载，占用http请求，影响性能。</p>

<p>鉴于此，yui将js的动态引入机制也并入其沙箱设计之中，我要引用的只是一个名字，比如slider.js，他依赖 tabview.js，tabview.js依赖tab.js，这样我只需引用一个名叫”slider”的东西即可，不用操心他依赖什么，更不用管如何引入到页面中，yui都帮我们搞定：</p>

<pre><code>TB.addmoudle({
    logicA:{
        fullpath:’logica.js’,
        requires:['slider']
    }
}).use(‘logicA’,function(Y){
    LogicA.start();
});
</code></pre>

<p>当我们看最终组成的页面的时候，看到的只是埋藏在页面各个角落的这些简短的结构一致的js代码段。很易理解，这点代码也不用像长的代码块压成一行。（更多细节可参照前端弱架构导致的代码污染 ）</p>

<h3>颗粒化 or http请求数</h3>

<p>这的确是一对矛盾，颗粒化带来了项目开发、管理、和代码重用的高效率，却又引入了更多的http请求数，好在yui提供了combo，可以将所有的 http请求合并成一个。只需在YUI引入的时候配置下combo属性即可，高颗粒化的请求数瞬间降低一倍以上。在之前做雅虎关系的时候，在yui2和 yui3pre并存的情况下，可以将请求降低到4个，yui2和3各一个种子，各自一个combo。当然这是在hack掉yui的loader的前提下。 yui默认不会合并非yui文件（更多细节可以阅读基于yui的团队开发）。即使这样，我们仍然可以控制我们的http请求数，在不hack yui的情况下，可以解决部分性能问题。</p>

<h3>懒惰加载 or 即时加载即时执行</h3>

<p>上文提到，逻辑依赖沙箱，沙箱依赖的js文件则是延时加载的，这样就导致一个问题，当页面比较庞大的时候，会等待页面js加载完毕才会渲染动作，这样的用户体验不佳，而即时加载即使运行则可以渲染出模块后随即渲染动作，当网速一定的时候，两者看似是一对不可调和的矛盾，yui 动态加载的机制比较折中的处理了这个问题，A逻辑需要a.js，B逻辑需要b.js，A逻辑则只会在a.js加载完成后执行，而不管b.js是否加载完成，而当A需要a.js和b.js的时候，A则需要等待a.js和b.js都加载完成才会执行，B逻辑只需判断当前是否已经加载b.js，如果b.js已经在其他模块中引入进来，B则可以立即执行。但确定的是，所有的js的引入一定是在domReady后执行的，也就是说，不管怎样，动作的渲染一定是在页面html结构出来后才开始执行的，用户体验上还是会有损失。</p>

<h3>面向接口的设计 or 面向dom的设计</h3>

<p>我们知道jquery的插件习惯将所有的动作都加载到一个$(‘#id’)上，使用的时候只要执行类似$(‘#id’).init()即可。这看起来简洁明快，开发者的逻辑的思路也始终没有离开“节点”，很方便理解，而对yui3 的node扩展就不那么方便，从yui3的pre版到正式版，对node扩展的方法在不断的修改（更多细节看这里：扩展yui3 node的定时器），这也可以看出yui设计者在对node扩展性设计时的纠结和苦恼：要不要允许开发者去扩展node节点呢？大概是因为设计者们对 prototype先天的弊端心有余悸。目前看来，设计者还没有完全走出纠结，尽管对node扩展相比yui3第一个预览版方便了很多，开发者仍然不能像写jquery插件那样优雅自如的对node进行扩展。相反，zakas却将更多的精力放在了widget接口的设计上，在这一点上，相比 jquery，yui3则具有无可限量的优越性，因为在yui3中，组件不仅仅是组件，而是一个有血有肉的生命体，他可以出生，可以成长，可以被改造，可以死亡，组件在这些复杂的运行时环境中自我锤炼，因此，一个复杂页面在yui3的技术体系中，变成了一个由无数组件链接而成的生态系统，这种生物链所带来的设计创新和新视野是其他js框架无论如何也无法超越的。关于yui3的组件开发更多细节可以参照：基于yui3的组件开发1 和 克军在D2上的分享《从yui2到yui3看前端的演变》</p>

<h3>苗条的身材 or 庞大的身躯</h3>

<p>说到这里，大概会有很多人拍砖。其实jquery和yui同属两类不同的js框架，一个苗条纤细，一个身重如山，两者之间其实没有什么水深火热，只是使用范围不同罢了，类似 jquery的轻框架的使用范围是博客级别的小网站，尤其适合单人开发，代码写一次不再修改，而且很适合初学者学习使用，给初学者带来自信。yui则使用与企业级的项目中的团队开发，项目维护周期远远超过开发周期，因此yui性能一定比不过jquery，jquery的续航能力也一定不如yui，没有最优秀，只有最适合。当然这自然也挡不住我个人对迷人的jquery的喜爱，只要我们能从各个js框架学到东西，能提高自己做前端架构的能力，就ok。</p>

<p>说了这么多，其实只有一个观点，人无完人，框架无完框架，缺憾之处必有权衡。以上YY，欢迎拍砖！</p>

</section>
<section class="meta">

<!--span class="tags">
  tagged by 
  
</span-->

<span class="time">
  posted at <time datetime="2010-08-03">2010-08-03</time>
</span>
</section>

<section class="comment">
<div id="disqus_thread"></div>
<script type="text/javascript">
    /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
    var disqus_shortname = 'jayliblog'; // required: replace example with your forum shortname
	var disqus_identifier = 'urn:uuid:0972f4e8-f0bf-4cf9-a07a-54392532ea05';

    /* * * DON'T EDIT BELOW THIS LINE * * */
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
    })();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</section>


        </article>
      </div>
    </div> <!--! end of #container -->
  </body>
</html>
